package com.simpou.commons.utils.file;

import com.simpou.commons.utils.file.FileHelper;
import com.simpou.commons.utils.file.PropertyHelper;
import com.simpou.commons.utils.file.filter.ContainsFileNameFilter;
import com.simpou.commons.utils.file.filter.ExtensionFileNameFilter;
import com.simpou.commons.utils.file.filter.PatternFileNameFilter;
import static com.simpou.commons.utils.lang.Randoms.*;
import com.simpou.commons.utils.tests.TestRunner;
import com.simpou.commons.utils.tests.annot.ClassTest;
import com.simpou.commons.utils.tests.annot.TestOrder;

import org.junit.AfterClass;
import static org.junit.Assert.*;

import org.junit.BeforeClass;
import org.junit.Test;

import org.junit.runner.RunWith;

import java.io.File;
import java.io.IOException;

import java.text.ParseException;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;


@RunWith(TestRunner.class)
@ClassTest(FileHelper.class)
public class FileHelperITCase {
    private static String rootDir;
    private static Map<String, String> filesStringMap;
    private static final int DIRS_LEVELS = 5;
    private static boolean isWindows;
    private final String fileExt1 = "txt";
    private final String fileExt2 = "html";
    private final String stringPattern = "strPttrn";

    @BeforeClass
    public static void setUpClass() throws Exception {
        rootDir = PropertyHelper.getTempDir() + getString(5, 10, false) +
            File.separator;
        FileHelper.mkdir(rootDir);
        isWindows = PropertyHelper.isWindows();
    }

    @AfterClass
    public static void tearDownClass() throws Exception {
        assertTrue(FileHelper.delete(rootDir));
    }

    @Test
    public void testWrite_5args() throws Exception {
        String text = "áàõụ" + getString(100, MAX_STRING_CHARS, true);
        String filePath = rootDir + getString(10, 20, false);
        FileHelper.write(filePath, text, false, false, FileHelper.UTF8_ENCODING);

        String textRead = FileHelper.read(filePath, FileHelper.UTF8_ENCODING);
        assertEquals(text, textRead);

        String textReadError = FileHelper.read(filePath, FileHelper.ISO_ENCODING);
        assertFalse(text.equals(textReadError));

        FileHelper.delete(filePath);
        assertFalse(FileHelper.exists(filePath));
    }

    @Test
    public void testWrite_4args() throws Exception {
        String text;
        boolean deleted;
        String filePath = rootDir + getString(10, 20, false);
        String content1 = getString(1, MAX_STRING_CHARS, true);
        String content2 = getString(1, MAX_STRING_CHARS, true);
        String newContent = content2 + content1;

        // testa escrita/leitura
        FileHelper.write(filePath, content2, false, false);
        text = new String(FileHelper.read(new File(filePath)));
        assertEquals(content2, text);

        // testa concatenação de conteúdo
        FileHelper.write(filePath, content1, true, false);
        text = FileHelper.read(filePath);
        assertEquals(newContent, text);

        // testa realização de backup
        String bkpFilePath = FileHelper.write(filePath, "", false, true);
        text = FileHelper.read(filePath);
        assertTrue(text.isEmpty());
        text = FileHelper.read(bkpFilePath);
        assertEquals(newContent, text);

        // limpa arquivos criados
        deleted = FileHelper.delete(filePath);
        assertTrue(deleted && !FileHelper.exists(filePath));
        deleted = FileHelper.delete(bkpFilePath);
        assertTrue(deleted && !FileHelper.exists(bkpFilePath));
    }

    @Test
    public void testMove() throws Exception {
        long length;
        String text;
        String name = getString(10, 20, false);
        String srcDirPath = rootDir + getString(5, 10, false) + File.separator;
        String tgtDirPath = rootDir + getString(5, 10, false) + File.separator;
        String srcFilePath = srcDirPath + name;
        String tgtFilePath = tgtDirPath + name;
        String content = getString(100, MAX_STRING_CHARS, true);

        // testa escrita/leitura
        FileHelper.write(srcFilePath, content, false, false);

        // testa rollback
        File file = new File(srcFilePath);
        file.setWritable(false);

        try {
            FileHelper.move(srcFilePath, tgtFilePath);
            fail();
        } catch (RuntimeException ex) {
        }

        assertFalse(FileHelper.exists(tgtFilePath));

        // move
        file.setWritable(true);
        length = FileHelper.length(srcFilePath);
        FileHelper.move(srcFilePath, tgtFilePath);
        text = FileHelper.read(tgtFilePath);
        assertEquals(content, text);
        assertFalse(FileHelper.exists(srcFilePath));
        assertEquals(length, FileHelper.length(tgtFilePath));
        assertTrue(FileHelper.delete(tgtDirPath));
        assertTrue(FileHelper.delete(srcDirPath));
    }

    @Test
    public void testLastModified()
        throws IOException, ParseException, InterruptedException {
        Date now1;
        Date now2;

        String filePath = rootDir + getString(5, 10, false);
        String content = getString(100, MAX_STRING_CHARS, true);
        String content2 = getString(100, MAX_STRING_CHARS, true);
        FileHelper.write(filePath, content, false, false);
        now1 = FileHelper.lastModified(filePath);

        Thread.sleep(getInteger(1000, 2000));

        FileHelper.write(filePath, content2, false, false);
        now2 = FileHelper.lastModified(filePath);
        assertTrue(now1.before(now2));
        assertTrue(FileHelper.delete(filePath));
    }

    @Test
    public void testRename() throws IOException, ParseException {
        long length;
        boolean renamed;
        String text;
        String srcName = getString(10, 20, false);
        String tgtName = getString(10, 20, false);
        String filePath = rootDir + getString(5, 10, false) + File.separator;
        String srcFilePath = filePath + srcName;
        String tgtFilePath = filePath + tgtName;
        String content = getString(100, MAX_STRING_CHARS, true);

        // cria arquivo a ser renomeado
        FileHelper.write(srcFilePath, content, false, false);

        // renomeia pro mesmo nome
        try {
            FileHelper.rename(srcFilePath, srcFilePath);
            fail();
        } catch (IllegalArgumentException e) {
        }

        // renomeia pra um diretório diferente
        try {
            FileHelper.rename(srcFilePath, "otherDir");
            fail();
        } catch (IllegalArgumentException e) {
        }

        if (!PropertyHelper.isWindows()) {
            // renomeia pra um arquivo ja existente
            File fileRoot = new File(filePath);
            fileRoot.setWritable(false);
            renamed = FileHelper.rename(srcFilePath, tgtFilePath);
            assertFalse(renamed);
            fileRoot.setWritable(true);
        }

        // renomeia
        length = FileHelper.length(srcFilePath);
        renamed = FileHelper.rename(srcFilePath, tgtFilePath);
        text = FileHelper.read(tgtFilePath);
        assertTrue(renamed);
        assertEquals(content, text);
        assertFalse(FileHelper.exists(srcFilePath));
        assertEquals(length, FileHelper.length(tgtFilePath));
        assertTrue(FileHelper.delete(filePath));
    }

    @Test
    public void testRead_error() throws Exception {
        String filePath;

        // arquivo inexistente
        filePath = rootDir + "a2#4$Rfgh.a";

        try {
            FileHelper.read(filePath);
            fail();
        } catch (IllegalArgumentException ex) {
        }

        // sem permissão de leitura
        filePath = rootDir + "unreadable";
        FileHelper.write(filePath, "unreadable", false, false);

        File file = new File(filePath);
        file.setReadable(false);
        assertFalse(!isWindows && file.canRead());

        try {
            FileHelper.read(filePath);
            assertTrue(isWindows); //prop não funciona no windows
        } catch (IllegalArgumentException ex) {
        } finally {
            FileHelper.delete(filePath);
        }
    }

    @Test
    @TestOrder(1)
    public void testList() throws Exception {
        String name;
        String text;
        String content;

        filesStringMap = new HashMap<String, String>();
        populateDirs(rootDir, 0);

        // todos arquivos recursivamente
        List<String> list1 = FileHelper.list(rootDir, true, null);
        Set<String> keySet = filesStringMap.keySet();
        assertEquals(keySet.size(), list1.size());

        // verifica conteúdo
        for (String string : keySet) {
            list1.remove(string);
            // testa leitura/escrita
            content = filesStringMap.get(string);

            // arquivo, diretorio n tem conteudo
            if (content != null) {
                text = FileHelper.read(string);
                assertEquals(content, text);
            }
        }

        assertTrue(list1.isEmpty());

        // todos arquivos de segundo nivel
        List<String> list2 = FileHelper.list(rootDir, false, null);
        File[] listFiles = new File(rootDir).listFiles();
        assertEquals(listFiles.length, list2.size());
        assertEquals(listFiles.length, FileHelper.count(rootDir));

        for (File file : listFiles) {
            list2.remove(file.getAbsolutePath());
        }

        assertTrue(list2.isEmpty());

        // todos arquivos com uma certa extensão
        List<String> list3 = FileHelper.list(rootDir, true,
                new ExtensionFileNameFilter(fileExt1));

        // todos arquivos com nome exato, case sensitive
        List<String> list4 = FileHelper.list(rootDir, true,
                new ContainsFileNameFilter(stringPattern, true, true));

        // todos arquivos com nome com certo padrão, case sensitive
        List<String> list5 = FileHelper.list(rootDir, true,
                new ContainsFileNameFilter(stringPattern, true, false));

        // todos arquivos com nome exato, case não sensitive
        List<String> list6 = FileHelper.list(rootDir, true,
                new ContainsFileNameFilter(stringPattern.toUpperCase(), false,
                    true));

        // todos arquivos que seguem um padrão
        String pattern = "^" + stringPattern + "\\w+" + "." + fileExt1 + "$";
        List<String> list7 = FileHelper.list(rootDir, true,
                new PatternFileNameFilter(pattern));

        for (String string : keySet) {
            name = FileHelper.name(string);

            if (name.endsWith("." + fileExt1)) {
                assertTrue(list3.contains(string));
            }

            if (name.equals(stringPattern)) {
                assertTrue(list4.contains(string));
            }

            if (name.contains(stringPattern)) {
                assertTrue(list5.contains(string));
            }

            if (name.equals(stringPattern) ||
                    name.equals(stringPattern.toUpperCase()) ||
                    name.equals(stringPattern.toLowerCase())) {
                assertTrue(list6.contains(string));
            }

            if (name.matches(pattern)) {
                assertTrue(list7.contains(string));
            }
        }

        boolean deleted;

        if (!PropertyHelper.isWindows()) {
            // testa falha na remoção de um arquivo na recursão
            File fileRoot = new File(rootDir);
            File[] files = fileRoot.listFiles();
            File file = null;

            for (File fileIter : files) {
                if (fileIter.isDirectory()) {
                    file = fileIter;

                    break;
                }
            }

            boolean setWritableOK = file.setWritable(false);

            if (setWritableOK) {
                deleted = FileHelper.delete(rootDir);
                assertTrue(FileHelper.exists(rootDir));
                assertFalse(deleted);
                file.setWritable(true);
            }
        }

        deleted = FileHelper.delete(rootDir);
        assertTrue(deleted && !FileHelper.exists(rootDir));
    }

    @Test
    @TestOrder(2)
    public void testList_error() throws Exception {
        // diretório inexistente
        try {
            FileHelper.list(rootDir + "error", true, null);
            fail();
        } catch (IllegalArgumentException e) {
        }

        // arquivo como diretório
        String path = rootDir + "file";
        FileHelper.write(path, "", false, false);
        assertTrue(FileHelper.exists(path));

        try {
            FileHelper.list(path, true, null);
            fail();
        } catch (IllegalArgumentException e) {
        }

        FileHelper.delete(path);
        assertFalse(FileHelper.exists(path));
    }

    @Test
    @TestOrder(3)
    public void testDelete() throws Exception {
        boolean deleted = FileHelper.delete(rootDir);
        assertTrue(deleted);
        assertFalse(FileHelper.exists(rootDir));
    }

    @Test
    @TestOrder(4)
    public void testDelete_error() throws Exception {
        File fileRoot = new File(rootDir);
        File file = new File(rootDir + "delete_error");
        FileHelper.write(file.getAbsolutePath(), "", false, false);
        assertTrue(file.exists());
        fileRoot.setWritable(false);

        try {
            FileHelper.delete(file.getAbsolutePath());
            assertTrue(isWindows);
        } catch (IllegalArgumentException ex) {
        } finally {
            fileRoot.setWritable(true);
            FileHelper.delete(file.getAbsolutePath());
            assertFalse(file.exists());
        }
    }

    private String getFileName() {
        String fileName;

        // cria file com nome com padrão de busca
        if (getBoolean() && getBoolean()) {
            // gera com padrão exato
            fileName = stringPattern;

            // gera com o padrão no início
            if (getBoolean()) {
                fileName += getString(3, 6, false);
            }
        } else {
            // gera um nome qualquer
            fileName = getString(1, 10, false);
        }

        return fileName;
    }

    private void populateFiles(String dir) throws IOException, ParseException {
        String fileContent;
        int items = getInteger(3, 5);
        Set<String> filePaths = new HashSet<String>();

        // lista arquivos a serem criados
        String filePathAux;

        while (filePaths.size() < items) {
            filePathAux = dir + getFileName();

            if (!filePathAux.endsWith(stringPattern)) {
                filePathAux += ".";
                filePathAux += (getBoolean() ? fileExt1 : fileExt2);
            }

            if (!FileHelper.exists(filePathAux)) {
                filePaths.add(filePathAux);
            }
        }

        //cria arquivo
        for (String filePath : filePaths) {
            fileContent = getString(1, MAX_STRING_CHARS, true);
            FileHelper.write(filePath, fileContent, false, false);
            assertTrue(FileHelper.exists(filePath));
            filesStringMap.put(filePath, fileContent);
        }
    }

    private void populateDirs(String dir, int level)
        throws IOException, ParseException {
        String dirPathAux;
        int dirs;
        Set<String> dirPaths;

        populateFiles(dir);

        // cria novo nível
        if (level < (DIRS_LEVELS - 1)) {
            dirs = getInteger(3, 5);

            // lista diretórios 
            dirPaths = new HashSet<String>();

            while (dirPaths.size() < dirs) {
                dirPathAux = dir + getFileName();

                if (!FileHelper.exists(dirPathAux)) {
                    dirPaths.add(dirPathAux);
                }
            }

            // cria diretórios 
            for (String dirPath : dirPaths) {
                FileHelper.mkdir(dirPath);
                assertTrue(FileHelper.exists(dirPath));
                filesStringMap.put(dirPath, null);
            }

            // popula diretórios 
            for (String dirPath : dirPaths) {
                populateDirs(dirPath + File.separator, level + 1);
            }
        }
    }

    @Test
    public void testExists() {
        //testReadWrite
    }

    @Test
    public void testCopy() {
        //testMove
    }

    @Test
    public void testLength() {
        //testMove
    }

    @Test
    public void testMkdir() {
        //setUpClass
    }

    @Test
    public void testCount() {
        //testList
    }

    @Test
    public void testName() {
        //testList
    }

    @Test
    public void testRead_String() throws Exception {
        //testWrite_4args
    }

    @Test
    public void testRead_File() throws Exception {
        //testWrite_4args
    }

    @Test
    public void testRead_String_String() throws Exception {
        //testWrite_5args
    }

    @Test
    public void testGetResourceFile() {
        //UTCase
    }

    @Test
    public void testGetDelimPattern_1() {
        //UTCase
    }

    @Test
    public void testGetDelimPattern_2() {
        //UTCase
    }

    @Test
    public void testPutDirPathEndDelim() throws IOException {
        //UTCase
    }

    @Test
    public void testNormalizeFilePathDelim() {
        //UTCase
    }
}
